View Javadoc
1   package org.apache.maven.surefire.report;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.List;
26  
27  import org.apache.maven.shared.utils.StringUtils;
28  
29  /**
30   * @author Kristian Rosenvold
31   */
32  @SuppressWarnings( "ThrowableResultOfMethodCallIgnored" )
33  public class SmartStackTraceParser
34  {
35  
36      private static final int MAX_LINE_LENGTH = 77;
37  
38      private final SafeThrowable throwable;
39  
40      private final StackTraceElement[] stackTrace;
41  
42      private final String simpleName;
43  
44      private String testClassName;
45  
46      private final Class testClass;
47  
48      private String testMethodName;
49  
50      public SmartStackTraceParser( Class testClass, Throwable throwable )
51      {
52          this( testClass.getName(), throwable, null );
53      }
54  
55      public SmartStackTraceParser( String testClassName, Throwable throwable, String testMethodName )
56      {
57          this.testMethodName = testMethodName;
58          this.testClassName = testClassName;
59          this.testClass = getClass( testClassName );
60          this.simpleName = this.testClassName.substring( this.testClassName.lastIndexOf( "." ) + 1 );
61          this.throwable = new SafeThrowable( throwable );
62          stackTrace = throwable.getStackTrace();
63      }
64  
65      private static Class getClass( String name )
66      {
67          try
68          {
69              ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
70              return classLoader != null ? classLoader.loadClass( name ) : null;
71          }
72          catch ( ClassNotFoundException e )
73          {
74              return null;
75          }
76      }
77  
78      private static String getSimpleName( String className )
79      {
80          int i = className.lastIndexOf( "." );
81          return className.substring( i + 1 );
82      }
83  
84      @SuppressWarnings( "ThrowableResultOfMethodCallIgnored" )
85      public String getString()
86      {
87          if ( testClass == null )
88          {
89              return throwable.getLocalizedMessage();
90          }
91  
92          StringBuilder result = new StringBuilder();
93          List<StackTraceElement> stackTraceElements = focusOnClass( stackTrace, testClass );
94          Collections.reverse( stackTraceElements );
95          StackTraceElement stackTraceElement;
96          if ( stackTraceElements.isEmpty() )
97          {
98              result.append( simpleName );
99              if ( StringUtils.isNotEmpty( testMethodName ) )
100             {
101                 result.append( "." ).append( testMethodName );
102             }
103         }
104         else
105         {
106             for ( int i = 0; i < stackTraceElements.size(); i++ )
107             {
108                 stackTraceElement = stackTraceElements.get( i );
109                 if ( i == 0 )
110                 {
111                     result.append( simpleName );
112                     if ( !stackTraceElement.getClassName().equals( testClassName ) )
113                     {
114                         result.append( ">" );
115                     }
116                     else
117                     {
118                         result.append( "." );
119                     }
120 
121                 }
122                 if ( !stackTraceElement.getClassName().equals( testClassName ) )
123                 {
124                     result.append( getSimpleName( stackTraceElement.getClassName() ) ); // Add the name of the superclas
125                     result.append( "." );
126                 }
127                 result.append( stackTraceElement.getMethodName() ).append( ":" ).append(
128                     stackTraceElement.getLineNumber() );
129                 result.append( "->" );
130             }
131 
132             if ( result.length() >= 2 )
133             {
134                 result.deleteCharAt( result.length() - 1 );
135                 result.deleteCharAt( result.length() - 1 );
136             }
137         }
138 
139         Throwable target = throwable.getTarget();
140         if ( target instanceof AssertionError )
141         {
142             result.append( " " );
143             result.append( throwable.getMessage() );
144         }
145         else if ( "junit.framework.AssertionFailedError".equals( target.getClass().getName() )
146             || "junit.framework.ComparisonFailure".equals( target.getClass().getName() ) )
147         {
148             result.append( " " );
149             result.append( throwable.getMessage() );
150         }
151         else
152         {
153             result.append( rootIsInclass() ? " " : " ยป " );
154             result.append( getMinimalThrowableMiniMessage( target ) );
155             result.append( getTruncatedMessage( MAX_LINE_LENGTH - result.length() ) );
156         }
157         return result.toString();
158     }
159 
160     private String getMinimalThrowableMiniMessage( Throwable throwable )
161     {
162         String name = throwable.getClass().getSimpleName();
163         if ( name.endsWith( "Exception" ) )
164         {
165             return StringUtils.chompLast( name, "Exception" );
166         }
167         if ( name.endsWith( "Error" ) )
168         {
169             return StringUtils.chompLast( name, "Error" );
170         }
171         return name;
172     }
173 
174     private String getTruncatedMessage( int i )
175     {
176         if ( i < 0 )
177         {
178             return "";
179         }
180         String msg = throwable.getMessage();
181         if ( msg == null )
182         {
183             return "";
184         }
185         String substring = msg.substring( 0, Math.min( i, msg.length() ) );
186         if ( i < msg.length() )
187         {
188             return " " + substring + "...";
189         }
190         else
191         {
192             return " " + substring;
193         }
194     }
195 
196     private boolean rootIsInclass()
197     {
198         return stackTrace.length > 0 && stackTrace[0].getClassName().equals( testClassName );
199     }
200 
201     static List<StackTraceElement> focusOnClass( StackTraceElement[] stackTrace, Class clazz )
202     {
203         List<StackTraceElement> result = new ArrayList<StackTraceElement>();
204         for ( StackTraceElement element : stackTrace )
205         {
206             if ( element != null && isInSupers( clazz, element.getClassName() ) )
207             {
208                 result.add( element );
209             }
210         }
211         return result;
212     }
213 
214     private static boolean isInSupers( Class testClass, String lookFor )
215     {
216         if ( lookFor.startsWith( "junit.framework." ) )
217         {
218             return false;
219         }
220         while ( !testClass.getName().equals( lookFor ) && testClass.getSuperclass() != null )
221         {
222             testClass = testClass.getSuperclass();
223         }
224         return testClass.getName().equals( lookFor );
225     }
226 
227     static Throwable findInnermostWithClass( Throwable t, String className )
228     {
229         Throwable match = t;
230         do
231         {
232             if ( containsClassName( t.getStackTrace(), className ) )
233             {
234                 match = t;
235             }
236 
237             t = t.getCause();
238 
239         }
240         while ( t != null );
241         return match;
242     }
243 
244     public static String innerMostWithFocusOnClass( Throwable t, String className )
245     {
246         Throwable innermost = findInnermostWithClass( t, className );
247         List<StackTraceElement> stackTraceElements = focusInsideClass( innermost.getStackTrace(), className );
248         String s = causeToString( innermost.getCause() );
249         return toString( t, stackTraceElements ) + s;
250     }
251 
252     static List<StackTraceElement> focusInsideClass( StackTraceElement[] stackTrace, String className )
253     {
254         List<StackTraceElement> result = new ArrayList<StackTraceElement>();
255         boolean found = false;
256         for ( StackTraceElement element : stackTrace )
257         {
258             if ( !found )
259             {
260                 result.add( element );
261             }
262 
263             if ( className.equals( element.getClassName() ) )
264             {
265                 if ( found )
266                 {
267                     result.add( element );
268                 }
269                 found = true;
270             }
271             else
272             {
273                 if ( found )
274                 {
275                     break;
276                 }
277             }
278         }
279         return result;
280     }
281 
282     static boolean containsClassName( StackTraceElement[] stackTrace, String className )
283     {
284         for ( StackTraceElement element : stackTrace )
285         {
286             if ( className.equals( element.getClassName() ) )
287             {
288                 return true;
289             }
290         }
291         return false;
292     }
293 
294     public static String causeToString( Throwable cause )
295     {
296         StringBuilder resp = new StringBuilder();
297         while ( cause != null )
298         {
299             resp.append( "Caused by: " );
300             resp.append( toString( cause, Arrays.asList( cause.getStackTrace() ) ) );
301             cause = cause.getCause();
302         }
303         return resp.toString();
304     }
305 
306     public static String toString( Throwable t, Iterable<StackTraceElement> elements )
307     {
308         StringBuilder result = new StringBuilder();
309         result.append( t.getClass().getName() );
310         result.append( ": " );
311         result.append( t.getMessage() );
312         result.append( "\n" );
313 
314         for ( StackTraceElement element : elements )
315         {
316             result.append( "\tat " ).append( element.toString() );
317             result.append( "\n" );
318         }
319         return result.toString();
320     }
321 }